/*
 * Board.h
 *
 *  Created on: Jun 26, 2009
 *      Author: mlrus
 */

#ifndef BOARD_H_
#define BOARD_H_

#include <iostream>
#include <fstream>
#include <string>
#include <set>
#include <vector>
#include <cstddef>

#include "Common.h"
#include "Storable.h"
#include "Cell.h"
#include "Axis.h"
#include "Util.h"

class Board: public Storable {

	void operator=(const Board&);

	public:

		enum SquareType {
			RG = 0x11, DL = 0x12, TL = 0x13, DW = 0x21, TW = 0x31
		};

	private:
		static const SquareType standardTiles[Common::boardSize * Common::boardSize];
		Cell *squares;
		Cell *priorSquares;
		void preservePriorsquares() {
			memcpy(priorSquares, squares, Common::boardSize * Common::boardSize * sizeof(Cell));
		}

	public:
		typedef const Cell * (Board::* const getNeighbor)(const Cell *);
		static const getNeighbor neighborAccess[4];
		static const getNeighbor neighborHorizontalAccess[2];
		static const getNeighbor neighborVerticalAccess[2];
		static const int numNeighbors;

		static const SquareType* const standardTilesStart();
		static const SquareType* const standardTilesEnd();

		Cell *firstSquare();
		Cell *lastSquare();

		inline Cell* getCell(uint row, uint col);
		inline void setCell(const Cell& c, uchar letter) {
			(const_cast<Cell *> (&c))->set(letter);
		}
		inline void setCell(Cell* c, uchar letter) {
			c->set(letter);
		}

		void initCell(Cell* c, uchar initMask,
				bool rowZero, bool rowN, bool colZero, bool colN);
		void retractCell(Cell& c);
		void cementTile(Cell& cell);

		//inline
		bool getEndpoints(const Cell* cell, Axis::Axis axis, Cell*& from, Cell*& to) {
			from = const_cast<Cell *> (cell);
			to = const_cast<Cell *> (cell);
			return getEndpoints(axis, from, to);
		}

		//inline
		bool getEndpoints(Axis::Axis axis, Cell*& from, Cell*& to) {
			switch(axis) {
				case Axis::HORIZONTAL:
					while(!from->colZero && validChar((from-1)->letter)){
						from--;
					}
					while(!to->colN && validChar((to+1)->letter)){
						to++;
					}
					break;
				case Axis::VERTICAL:
					int incr = getIncrement(axis);
					while(!from->rowZero && validChar((from-incr)->letter)) {
						from-=incr;
					}
					while(!to->rowN && validChar((to+incr)->letter)) {
						to+=incr;
					}
					break;
				default:
					cerr << "Error: invalid direction." << endl;
					return false;
			}
			return true;
		}

		void cementWord(Cell* from, Cell* to);

		inline const bool validCell(const Cell *cell) const {
			return cell >= squares && cell < squares + Common::boardSize * Common::boardSize;
		}

		inline const int row(const Cell *cell) const {
			Cell * sq = squares;
			ptrdiff_t diff = cell - sq;
			return diff / Common::boardSize;
		}

		inline const int col(const Cell *cell) const {
			return (cell - squares) % Common::boardSize;
		}

		inline const Cell* getLeft(const Cell *cell) {
			if (col(cell) > 0)
				return cell - 1;
			return NULL;
		}

		inline const Cell* getRight(const Cell * cell) {
			if (col(cell) < Common::boardSize - 1)
				return cell + 1;
			return NULL;
		}

		inline const Cell* getUp(const Cell * cell) {
			if (row(cell) > 0)
				return cell - Common::boardSize;
			return NULL;
		}

		inline const Cell* getDown(const Cell * cell) {
			if (row(cell) < Common::boardSize - 1)
				return cell + Common::boardSize;
			return NULL;
		}

		inline const Axis::Axis getAxis(const Cell* head, const Cell* tail);

		inline const uint getIncrement(Axis::Axis axis) {
			return (axis == Axis::VERTICAL) ? Common::boardSize : 1;
		}

		inline bool validChar(uchar letter) {
			return letter != Common::EMPTYCHAR;
		}

		uint scoreMove(const Cell* cell, Axis::Axis axis);
		bool changed(const Cell* cell);
		uint scoreAxis(const Cell* cell, Axis::Axis axis, string prefix = "");

		bool hasAdjacent(const Cell *cell);
		bool hasAdjacent(const Cell *cell, Axis::Axis axis);
		const string formatBoard();
		const string toString();
		string describeCell(Cell *cell) const;
		string describeCell(const Cell *cell) const;
		void read(const string& filename);
		const string& cleanString(string& string, const string& valid, uchar replacement);
		const string getBoardString();
		void reset();

		Board(const Board &b) :
			Storable(), squares(b.squares), priorSquares(b.priorSquares) {
		}

		Board(string& filename) :
			Storable(), squares(new Cell[Common::boardSize * Common::boardSize]), priorSquares(
					new Cell[Common::boardSize * Common::boardSize]) {
			Cell *cptr = squares;
			for (const SquareType *tile = standardTilesStart(); tile < standardTilesEnd(); tile++) {
				SquareType sq = *tile;
				initCell(cptr, sq, row(cptr)==0, row(cptr)==Common::boardSize-1,
						col(cptr)==0, col(cptr)==Common::boardSize-1);
				cptr++;
			}
			read(filename);
			preservePriorsquares();
		}

		virtual ~Board() {
			delete[] squares;
			delete[] priorSquares;
		}
};

#endif /* BOARD_H_ */
